//##############################################################################
//#    CuteWebshot is an application which can be used to render
//#    screenshots from webpages.
//#
//#    Copyright (C) 2010  Juhapekka Piiroinen
//#
//#    This program is free software: you can redistribute it and/or modify
//#    it under the terms of the GNU General Public License as published by
//#    the Free Software Foundation, either version 3 of the License, or
//#    (at your option) any later version.
//#
//#    This program is distributed in the hope that it will be useful,
//#    but WITHOUT ANY WARRANTY; without even the implied warranty of
//#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//#    GNU General Public License for more details.
//#
//#    You should have received a copy of the GNU General Public License
//#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//#############################################################################
//# Contact: juhapekka.piiroinen@gmail.com
//#############################################################################
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    FUNC_IN;
        ui->setupUi(this);
        m_bAboutToClose = false;

        ///////////////////////////
        // lets center the window
        int width = QApplication::desktop()->screen()->width();
        int height = QApplication::desktop()->screen()->height();
        this->move(width/2-this->width()/2,height/2-this->height()/2);

        /////////////////////////////////////
        // lets load the target path value
        loadSettings();

        ///////////////////////////////////////
        // lets initialize the parallel processing spinbox timer
        m_timerParallelProcessingChanged = new QTimer(this);
        m_timerParallelProcessingChanged->setInterval(3000);

        ///////////////////////////////////////
        // lets initialize statusbar widgets
        m_labelUrlsInQueue = new QLabel();
        m_labelCurrentlyProcessing = new QLabel();
        m_spinBoxParallelProcesses = new QSpinBox();
        m_spinBoxParallelProcesses->setFrame(false);
        m_spinBoxParallelProcesses->setValue(1);
        m_spinBoxParallelProcesses->setMinimum(1);
        m_spinBoxParallelProcesses->setStyleSheet("background: transparent");
        m_labelProcessedUrls = new QLabel();
        m_labelProcessingFailed = new QLabel();

        ///////////////////////////////////////
        // lets move some of the widgets to statusbar
        ui->statusBar->addWidget(m_labelUrlsInQueue);
        ui->statusBar->addWidget(m_labelCurrentlyProcessing);
        ui->statusBar->addWidget(m_spinBoxParallelProcesses);
        ui->statusBar->addWidget(m_labelProcessedUrls);
        ui->statusBar->addWidget(m_labelProcessingFailed);


        ///////////////////////////////////////
        // lets define the executable
        m_webshotApp = QApplication::applicationDirPath()+QDir::separator()+"CuteWebshotRenderer";

#ifdef __WIN32__
        m_webshotApp += ".exe";
#endif
        ///////////////////////////////////////
        // lets initialize the background import thread
        m_fileOpenThread = new FileToStringListThread();
        connect(m_fileOpenThread,SIGNAL(ready(QStringList)),this,SLOT(handleFileOpened(QStringList)));


        /////////////////////////////////////
        // lets initialize file import splashscreen
        QPixmap pixmap(":/importsplash.jpg");
        m_importSplash = new QSplashScreen(this,pixmap);
        connect(m_fileOpenThread,SIGNAL(status(QString)),m_importSplash,SLOT(showMessage(QString)));

        ///////////////////////////////////////
        // lets initialize the stringlistmodels which holds the data
        // note that we can use these models later on for view-model ui elements
        m_urlQueue = new QStringListModel();
        m_urlRendered = new QStringListModel();
        m_urlFailed = new QStringListModel();

        ///////////////////////////////////////
        // lets connect the automatic processing related signals to corresponding slots
        ///////////////////////////////////////

        ///////////////////////////////////////
        // lets disable <process next> when automatic processing is enabled
        connect(ui->actionAutomatic_Processing,SIGNAL(toggled(bool)),ui->actionProcess_Next,SLOT(setDisabled(bool)));

        ///////////////////////////////////////
        // lets do something when user toggles the automatic processing
        connect(ui->actionAutomatic_Processing,SIGNAL(triggered()),this,SLOT(handleAutomaticProcessing()));

        ///////////////////////////////////////
        // lets connect spinbox parallel processing and related timer
        connect(this->m_spinBoxParallelProcesses,SIGNAL(valueChanged(int)),this,SLOT(handleParallelProcessCountChanged(int)));
        connect(this->m_timerParallelProcessingChanged,SIGNAL(timeout()),this,SLOT(on_actionProcess_Next_triggered()));

        /////////////////////////
        // lets update labels
        updateLabels();
        QString htmlWelcome = "";
        htmlWelcome += "<p><b>CuteWebshot</b> is an open source application for website rendering.";
        htmlWelcome += "<p><b>Framework</b> Qt 4.7.x";
        htmlWelcome += "<p><b>License</b> GNU General Public License v3</p>";
        htmlWelcome += "<p><b>More information</b><br />";
        htmlWelcome += "<pre>";
        htmlWelcome += "     http://code.google.com/p/cutewebshot/<br />";
        htmlWelcome += "     http://qt.nokia.com/<br />";
        htmlWelcome += "     http://www.gnu.org/licenses/gpl.html<br />";
        htmlWelcome += "</pre>";

        ui->textEditWelcome->setHtml(htmlWelcome);
    FUNC_OUT;
}

void MainWindow::saveSettings() {
    FUNC_IN;
        QSettings settings;

        ////////////////////////////////////////////////
        // lets save the settings
        settings.setValue(DEF_TARGET_PATH,ui->lineEditTargetPath->text());
        settings.setValue(DEF_SKIP_IF_RENDERED,ui->checkBoxSkipIfAlreadyRendered->isChecked());

        ////////////////////////////////////////////////
        // lets write the changes to permanent storage
        settings.sync();
    FUNC_OUT;
}

void MainWindow::loadSettings() {
    FUNC_IN;
        //////////////////////////////////////
        // lets load the target temporary path
        QSettings settings;
#ifdef __WIN32__
        ui->lineEditTargetPath->setText(settings.value(DEF_TARGET_PATH,QString("C:\\Temp\\")).toString());
#else
        ui->lineEditTargetPath->setText(settings.value(DEF_TARGET_PATH,QString("/tmp/")).toString());
#endif

        ///////////////////////////////////////
        // lets load the checkbox value for skip on existing rendered pic
        ui->checkBoxSkipIfAlreadyRendered->setChecked(settings.value(DEF_SKIP_IF_RENDERED,false).toBool());

    FUNC_OUT;
}

void MainWindow::handleParallelProcessCountChanged(int) {
    FUNC_IN;
        ///////////////////////////////////////
        // lets use a timer to start new processing lines
        if (m_timerParallelProcessingChanged->isActive()==false) {
            m_timerParallelProcessingChanged->start();
        }
    FUNC_OUT;
}

void MainWindow::commitData(QSessionManager& manager)
{
    //FIXME: currently should be called from main.cpp, investigate more
    FUNC_IN;
        //http://doc.qt.nokia.com/4.7/qsessionmanager.html#allowsInteraction
        if (manager.allowsInteraction()) {
            int ret = QMessageBox::warning(
                        this,
                        tr(QApplication::applicationName().toAscii().data()),
                        tr("Do you want to save the queue?"),
                        QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);

            switch (ret) {
            case QMessageBox::Save:
                manager.release();
                // TODO: to be implemented
                DEBUG << "save not implemented";
                break;
            case QMessageBox::Discard:
                break;
            case QMessageBox::Cancel:
            default:
                manager.cancel();
            }
        } else {
            //TODO: save something automatically to cache
            //      then on next start ask from user
            //      if he/she wants to recover last session
        }
    FUNC_OUT;
}

void MainWindow::on_actionClear_Queue_triggered() {
    FUNC_IN;
        m_urlQueue->setStringList(QStringList());
        updateLabels();
    FUNC_OUT;
}

void MainWindow::closeEvent(QCloseEvent* event) {
    Q_UNUSED(event);
    FUNC_IN;

        //////////////////////////////////////
        // lets save the application settings before it is closed
        saveSettings();

        //////////////////////////////////
        // lets raise a flag that we are about to close
        // this flag should be monitored in threads.
        m_bAboutToClose = true;

        //////////////////////////////////
        // lets kill all running processes, a way to give heads-up
        foreach(QProcess* process, m_renderingPipeline) {
            process->kill();
            process->deleteLater();
        }
        foreach (QTimer* pTimer, m_renderingKillSwitch) {
            pTimer->stop();
            pTimer->deleteLater();
        }

        m_fileOpenThread->stop();
        m_timerParallelProcessingChanged->stop();

    FUNC_OUT;
}

MainWindow::~MainWindow()
{
    FUNC_IN;
        delete ui;
        delete m_urlQueue;
        delete m_urlRendered;
        delete m_urlFailed;
        delete m_importSplash;

        foreach (QProcess* pProcess, m_renderingPipeline) {
            if (pProcess) {
                delete pProcess; pProcess = NULL;
            }
        }
        foreach (QTimer* pTimer, m_renderingKillSwitch) {
            if (pTimer) {
                delete pTimer; pTimer = NULL;
            }
        }

        delete m_fileOpenThread;
        delete m_timerParallelProcessingChanged;

        //////////////////////
        // widgets for statusbar
        delete m_labelUrlsInQueue;
        delete m_labelCurrentlyProcessing;
        delete m_labelProcessedUrls;
        delete m_labelProcessingFailed;
        delete m_spinBoxParallelProcesses;

    FUNC_OUT;
}

bool MainWindow::hasBlockingThreads() {
    FUNC_IN;
        bool retval = m_fileOpenThread->isRunning();
        if (retval) {
            DEBUG << "blocking thread detected";
            ui->actionAutomatic_Processing->setChecked(false);
        } else {
            DEBUG << "no blocking threads";
        }
    FUNC_OUT;
    return retval;
}

void MainWindow::handleFileOpened(QStringList list) {
    Q_UNUSED(list);
    FUNC_IN;
        this->setWindowTitle(DEF_APPLICATION_TITLE);
        this->setEnabled(true);
        updateLabels();
        //////////////////////////////
        // lets alert the user that the file open has been processed.
        // really user friendly on long imports etc..
        QApplication::alert(this);

        if (m_importSplash->isVisible()) {
            m_importSplash->hide();
        }
    FUNC_OUT;
}

void MainWindow::handleAutomaticProcessing() {
    FUNC_IN;
        CHECK_BLOCKING_THREADS;

        ///////////////////////////////////////
        // lets check if the automatic processing is enabled
        // this needs to be done as we call this function
        // after each completed processing
        if (ui->actionAutomatic_Processing->isChecked()==false) {
            FUNC_OUT;
            return;
        }

        ///////////////////////////////////////
        // lets check how many free processing pipelines we have
        int freeLines = m_spinBoxParallelProcesses->value() - m_renderingPipeline.count();

        ///////////////////////////////////////
        // if we have any, lets fill them up
        while (freeLines > 0) {
            QTimer::singleShot(100,this,SLOT(on_actionProcess_Next_triggered()));
            freeLines--;
        }

        ///////////////////////////////////////
        // if we have emptied the queue, lets disable the automatic processing
        if (m_urlQueue->stringList().count() == 0) {
            ui->actionAutomatic_Processing->setChecked(false);
        }
    FUNC_OUT;
}

void MainWindow::on_actionFrom_wget_to_queue_triggered() {
    FUNC_IN;
        CHECK_BLOCKING_THREADS;
        // TODO: this should be moved to separate thread.

        ///////////////////////////////////////
        // lets get the filename from the user
        QString filename = QFileDialog::getOpenFileName(this,"Import URL list from file");

        ///////////////////////////////////////
        // lets check if the user cancelled action
        if (filename!=NULL) {
            ///////////////////////////////////////
            // user selected a file
            ///////////////////////////////////////

            ///////////////////////////////////////
            // lets check if it exists
            DEBUG << "filename" << filename;
            if (QFile::exists(filename)) {    
                DEBUG << "file exists";
                QFile fileImport(filename);
                QString readData;

                ///////////////////////////////////////
                // lets open the file for readonly and convert \n\r to \n automatically
                if (fileImport.open(QIODevice::ReadOnly | QIODevice::Text)) {
                    DEBUG << "file opened for readonly";
                    readData = QString(fileImport.readAll());
                    fileImport.close();
                    DEBUG << "file closed";
                } else {
                    FAIL << "file could not be opened for readonly";
                }

                ///////////////////////////////////////
                // lets do the data processing after we have closed the file
                // so we need to check if something was actually read from file at all
                if (readData.length()>0) {

                    DEBUG << "lets process the read data";

                    ///////////////////////////////////////
                    // lets split data into lines, note that QIODevice::Text has converted the line-ends.
                    QStringList newUrls = readData.split("\n");

                    ///////////////////////////////////////
                    // lets get a temporary copy of the current url queue
                    QStringList queue = m_urlQueue->stringList();

                    ///////////////////////////////////////
                    // lets go thru the imported urls
                    foreach (QString url, newUrls) {
                        ///////////////////////////////////////
                        // lets get a _valid_ url from _user_ input
                        QUrl tmpUrl = QUrl::fromUserInput(url);

                        // lets check that the current url is valid
                        if (tmpUrl.isValid() && tmpUrl.isRelative()==false) {
                            ///////////////////////////////////////
                            // url was valid, lets add it to the queue
                            DEBUG << "valid url found:" << url;
                            queue << url;
                        }
                    }

                    ///////////////////////////////////////
                    // lets set the temporary copy of the queue as real one
                    m_urlQueue->setStringList(queue);

                    DEBUG << "data processed and added to queue";
                }
            }

        }
    FUNC_OUT;
}

void MainWindow::appendUrlToQueue(QString url) {
    FUNC_IN;
        QStringList items = m_urlQueue->stringList();
        items << url;
        ui->lineEditNewUrlToQueue->clear();
        m_urlQueue->setStringList(items);
    FUNC_OUT;
}

void MainWindow::on_lineEditNewUrlToQueue_returnPressed() {
    FUNC_IN;
        CHECK_BLOCKING_THREADS;

        ///////////////////////////////////////
        // lets add the new url to queue
        this->appendUrlToQueue(ui->lineEditNewUrlToQueue->text());

        ///////////////////////////////////////
        // if queue has only one item then start to process it
        if (m_urlQueue->rowCount()==1) {
            on_actionProcess_Next_triggered();
        }

        ///////////////////////////////////////
        // lets update labels
        updateLabels();
    FUNC_OUT;
}

QString MainWindow::getNextUrlFromQueue() {
    FUNC_IN;
        QStringList items = m_urlQueue->stringList();
        QString retval = items.takeFirst();
        m_urlQueue->setStringList(items);
    FUNC_OUT;
    return retval;
}

void MainWindow::on_actionProcess_Next_triggered() {
    FUNC_IN;
        CHECK_BLOCKING_THREADS;

        QStringList items = m_urlQueue->stringList();
        if (items.count()>0) {
            ///////////////////////////////////////
            // lets check that we are not in the process limit
            if (m_url.count()>=m_spinBoxParallelProcesses->value()) {
                FAIL << "Parallel process limit exceeded!";
                FUNC_OUT;
                return;
            }

            ///////////////////////////////////////
            // lets get the next url from queue
            QString sNextUrl = getNextUrlFromQueue();

            ///////////////////////////////////////
            // lets update the labels
            updateLabels();

            ///////////////////////////////////////
            // lets check that we have not already rendered the url
            QString path = ui->lineEditTargetPath->text();
            QString filename = path+sNextUrl.toAscii().toBase64()+".png";
            if (QFile::exists(filename) && ui->checkBoxSkipIfAlreadyRendered->isChecked()) {
                DEBUG << sNextUrl << "is already rendered.";
                appendProcessedUrl(sNextUrl);
                updateLabels();
                QTimer::singleShot(500,this,SLOT(on_actionProcess_Next_triggered()));
                FUNC_OUT;
                return;
            }

            ///////////////////////////////////////
            // lets create the process and connect it
            QProcess* pNewRenderingPipeLine = new QProcess(this);
            connect(pNewRenderingPipeLine,SIGNAL(finished(int)),this,SLOT(renderingComplete(int)));

            ///////////////////////////////////////
            // lets create a kill switch / monitor for the process
            QTimer* pKillSwitch = new QTimer(this);
            connect(pKillSwitch,SIGNAL(timeout()),pNewRenderingPipeLine,SLOT(kill()));
            pKillSwitch->setInterval(KILL_SWITCH_TIMEOUT);

            ///////////////////////////////////////
            // lets add the previously fetched url to the process
            QStringList arguments;
            arguments << sNextUrl;
            m_url.append(sNextUrl);

            ///////////////////////////////////////
            // lets update labels
            updateLabels();

            ///////////////////////////////////////
            // lets add the created process and killswitch to a list for later usage
            this->m_renderingKillSwitch.append(pKillSwitch);
            this->m_renderingPipeline.append(pNewRenderingPipeLine);

            ///////////////////////////////////////
            // lets start the process
            pNewRenderingPipeLine->start(m_webshotApp,arguments);
            pKillSwitch->start();
        }
    FUNC_OUT;
}

void MainWindow::on_actionFrom_csv_to_queue_triggered() {
    FUNC_IN;
        CHECK_BLOCKING_THREADS;

        //"http://s3.amazonaws.com/alexa-static/top-1m.csv.zip";
        QString filename = QFileDialog::getOpenFileName(this,"Import URL list from csv","","*.csv");
        if (filename!=NULL) {
            if (QFile::exists(filename)) {
                ///////////////////////////////
                // lets disable ui for the import
                this->setWindowTitle(QString("%0 - Importing..").arg(DEF_APPLICATION_TITLE));
                this->setEnabled(false);

                // lets display the splash screen
                m_importSplash->show();
                m_importSplash->showMessage("Importing urls from alexa list..");

                ///////////////////////////////////////
                // lets set the filename and start the import thread
                // this is required to have an fluent ui, which does not hang
                // tested with Alexa top 1m list
                m_fileOpenThread->setFileName(filename);
                m_fileOpenThread->setStringListModel(m_urlQueue);
                m_fileOpenThread->start();
            }
        }
    FUNC_OUT;
}


void MainWindow::on_actionQueue_to_file_triggered() {
    FUNC_IN;
        CHECK_BLOCKING_THREADS;

        ///////////////////////////////////////
        // lets ask from the user a target filename in which we should save the data
        QString filename = QFileDialog::getSaveFileName(this,"Export URL list to file");

        ///////////////////////////////////////
        // lets check if user selected something
        if (filename!=NULL) {

            ///////////////////////////////////////
            // lets check if it is an existing file
            if (QFile::exists(filename)) {
                // TODO: ask do you want to replace
                DEBUG << "TODO: do you want to replace";
            }

            ///////////////////////////////////////
            // lets set the file object
            QFile fileWrite;
            fileWrite.setFileName(filename);

            // lets open the file
            if (fileWrite.open(QIODevice::WriteOnly)) {

                ///////////////////////////////////////
                // lets write the urls from queue to a file
                int i = 0;
                foreach (QString url, m_urlQueue->stringList()) {
                    ///////////////////////////////////////
                    // lets write a url to buffer
                    fileWrite.write((url+"\n").toAscii());

                    ///////////////////////////////////////
                    // lets flush the buffer to file on each 100 urls.
                    if (i % 100 == 0) {
                        fileWrite.flush();
                    }
                    i++;
                }
                ///////////////////////////////////////
                // lets close and flush the buffer to file
                fileWrite.close();

                DEBUG << i << "urls written";
            }
        } else {
            DEBUG << "user cancelled";
        }
    FUNC_OUT;
}

void MainWindow::renderingComplete(int exitcode) {
    FUNC_IN;
        DEBUG << "process exit code is" << exitcode;

        ///////////////////////////////////////
        // lets return if we are about to leave
        if (this->m_bAboutToClose) {
            FUNC_OUT;
            return;
        }

        ///////////////////////////////////////
        // lets get the process and killswitch which ended
        QProcess* pNewRenderingPipeLine = qobject_cast<QProcess*>(sender());
        int index = m_renderingPipeline.indexOf(pNewRenderingPipeLine);
        QTimer* pKillSwitch = m_renderingKillSwitch.takeAt(index);

        ///////////////////////////////////////
        // lets stop the kill switch and delete it
        pKillSwitch->stop();
        disconnect(pKillSwitch,SIGNAL(timeout()),pNewRenderingPipeLine,SLOT(kill()));
        pKillSwitch->deleteLater();

        ///////////////////////////////////////
        // lets disconnect and delete the process
        disconnect(pNewRenderingPipeLine,SIGNAL(finished(int)),this,SLOT(renderingComplete(int)));
        this->m_renderingPipeline.removeAt(index);
        pNewRenderingPipeLine->deleteLater();

        ///////////////////////////////////////
        // lets check which url we were processing in the process
        QString url = m_url.takeAt(index);

        ///////////////////////////////////////
        // lets check if the processing went ok
        if (exitcode==0) {

            ///////////////////////////////////////
            // lets check if the image was rendered
            QString path = ui->lineEditTargetPath->text();
            QString filename = path+url.toAscii().toBase64()+".png";
            if (QFile::exists(filename)) {
                appendProcessedUrl(url);
            } else {
                FAIL << url << "was not rendered!";
                appendFailedUrl(url);
            }
        } else {
            FAIL << "url" << url << "processing failed";
            appendFailedUrl(url);
        }

        ///////////////////////////////////////
        // lets update labels
        updateLabels();

        ///////////////////////////////////////
        // lets call the automatic processing, when done, this should keep constant amount of processes running
        // all related checks are done inside the function itself..
        handleAutomaticProcessing();
    FUNC_OUT;
}

void MainWindow::appendFailedUrl(QString url) {
    FUNC_IN;
        QStringList failedUrls = m_urlFailed->stringList();
        failedUrls << url;
        m_urlFailed->setStringList(failedUrls);
    FUNC_OUT;
}

void MainWindow::appendProcessedUrl(QString url) {
    FUNC_IN;
        QStringList renderedUrls = m_urlRendered->stringList();
        renderedUrls << url;
        m_urlRendered->setStringList(renderedUrls);
    FUNC_OUT;
}

void MainWindow::on_lineEditTargetPath_returnPressed() {
    FUNC_IN;
        saveSettings();
    FUNC_OUT;
}

void MainWindow::updateLabels() {
    FUNC_IN;
        m_labelUrlsInQueue->setText(QString("Queue: %0").arg(m_urlQueue->rowCount()));
        m_labelCurrentlyProcessing->setText(QString("Running: %0").arg(m_url.count()));
        m_labelProcessedUrls->setText(QString("Pass: %0").arg(m_urlRendered->stringList().size()));
        m_labelProcessingFailed->setText(QString("Failed: %0").arg(m_urlFailed->stringList().size()));
    FUNC_OUT;
}
